package com.integration.maven.processor.processor;


import com.google.auto.service.AutoService;
import com.integration.maven.processor.annotation.Dto;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.MirroredTypeException;
import javax.tools.Diagnostic;
import java.util.Set;

/**
 * @Author ckk
 * @Date 2020/9/12 15:21
 * @Description 功能描述:  APT
 **/
//  声明处理的 注解
@SupportedAnnotationTypes({"com.integration.maven.processor.annotation.Dto"})
//  jdk8 以上
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//  自动添加配置文件
@AutoService(Processor.class)
public class DtoProcessor extends AbstractProcessor {


    private JavacTrees trees;
    private TreeMaker treeMaker;
    private Names names;
    // 打印信息使用
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.trees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
        messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        // 从 Element 转 tree 的工具
        final JavacElements elementUtils = (JavacElements) processingEnv.getElementUtils();
        // 循环注解元素
        for (Element element : roundEnv.getElementsAnnotatedWith(Dto.class)) {
            // 异常类
            String exception;
            try {
                exception = element.getAnnotation(Dto.class).exceptionClass().getName();
            } catch (MirroredTypeException e) {
                exception = e.getTypeMirror().toString();
            }
            // 可为空参数
            String[] canNull = element.getAnnotation(Dto.class).canNull();
            // 获取主节点 class
            Element enclosingElement = element.getEnclosingElement();
            // 获取 class 的节点树
            JCTree.JCClassDecl classTree = (JCTree.JCClassDecl) trees.getTree(enclosingElement);
            // 变化泛型
            String fan = classTree.typarams.get(0).name.toString();
            // 固定泛型
//            if(!"T".equals(classTree.typarams.get(0).name.toString())){
//                messager.printMessage(Diagnostic.Kind.ERROR, classTree.getSimpleName()+"泛型： 必须固定是 T ，而不是 "+classTree.typarams.get(0).name.toString());
//            }


            // 处理生成新的 Dto 转化 方法
            try {
                classTree.defs = classTree.defs.prepend(makeDtoMethodDecl((JCTree.JCMethodDecl) elementUtils.getTree(element),
                        exception, canNull, fan));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }


        return true;
    }


    // 生成 dto 方法
    private JCTree.JCMethodDecl makeDtoMethodDecl(JCTree.JCMethodDecl jcMethodDecl, String exceptionName, String[] canNull,String fan) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

        String dto = "dto";

        //方法的访问级别
        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);

        //方法名称
        Name methodName = jcMethodDecl.name;

        //设置返回值 调用 真实的方法
        JCTree.JCExpression returnMethodType = jcMethodDecl.restype;
        // messager.printMessage(Diagnostic.Kind.ERROR, "类型："+"void".equals(returnMethodType.toString()));
        // 方法体列表
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();

        // 判断dto 不为null，对应参数不为null
        // 添加方法体中
        statements.add(treeMaker.If(
                // if 判断条件
                treeMaker.Parens(
                        treeMaker.Binary(
                                // 等于
                                JCTree.Tag.EQ,
                                // 指定哪个变量
                                treeMaker.Ident(getNameFromString(dto)),
                                // null值
                                treeMaker.Literal(TypeTag.BOT, null))
                ),
                // if的方法体
                treeMaker.Throw(treeMaker.NewClass(null,
                        // 参数
                        List.of(memberAccess("java.lang.String")),//参数类型
                        // new 的class
                        memberAccess(exceptionName),
                        // 参数值
                        List.of(treeMaker.Literal("dto不能为null")),
                        null)),
                // else的方法体
                null
        ));

        // 循环字段判断不为空
        for (JCTree.JCVariableDecl var : jcMethodDecl.params) {
            // 泛型不处理
            if(fan.equals(var.vartype.toString())){
                continue;
            }
            // 可为null 不判断, 不存在数组里，则需要判断
            if (!isExist(canNull, var.name.toString())) {
                // 获取参数方法的表达式
                JCTree.JCExpression var_d = treeMaker.Apply(
                        // 没有参数类型
                        List.nil(),
                        // 指定 dto 里面 的哪个get方法
                        treeMaker.Select(treeMaker.Ident(names.fromString(dto)), getMethodName(var.name)),
                        // 没有参数值
                        List.nil()
                );
                // 添加方法体中
                statements.add(treeMaker.If(
                        // if 判断条件
                        treeMaker.Parens(
                                treeMaker.Binary(
                                        // 等于
                                        JCTree.Tag.EQ,
                                        // 指定哪个变量
                                        var_d,
                                        // null值
                                        treeMaker.Literal(TypeTag.BOT, null))
                        ),
                        // if的方法体
                        treeMaker.Throw(treeMaker.NewClass(null,
                                // 参数
                                List.of(memberAccess("java.lang.String")),//参数类型
                                // new 的class
                                memberAccess(exceptionName),
                                // 参数值
                                List.of(treeMaker.Literal(var.name.toString() + ":参数不能为null")),
                                null)),
                        null
                ));

            }
        }
        // return 返回的列表 类型
        ListBuffer<JCTree.JCExpression> var_p = new ListBuffer<>();
        // return 返回的列表 值
        ListBuffer<JCTree.JCExpression> var_v = new ListBuffer<>();
        // 循环入参 列表
        for (JCTree.JCVariableDecl var : jcMethodDecl.params) {
            // 是泛型 则，直接传dto
            if(fan.equals(var.vartype.toString())){
                var_p.add(var.vartype);
                var_v.add( treeMaker.Ident(getNameFromString(dto)));
            }else{
                // 不是泛型，传的是 dto里面参数对应的get方法
                JCTree.JCExpression var_d  = treeMaker.Apply(
                        List.nil(),
                        treeMaker.Select(treeMaker.Ident(names.fromString(dto)), getMethodName(var.name)),
                        List.nil()
                );
                var_p.add(var.vartype);
                var_v.add(var_d);
            }
        }
        // 设置返回是调用 某个方法，并把以上的参数类型列表和参数值列表传入
        JCTree.JCExpression printVar = treeMaker.Apply(
                //参数类型
                List.from(var_p),
                // 返回哪个方法
                treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcMethodDecl.getName()),
                // 参数值列表
                List.from(var_v)
        );
        if("void".equals(returnMethodType.toString())){
            // 直接调用方法，不用返回
            statements.append(treeMaker.Exec(printVar));
        }else{
            // 指定返回
            statements.append(treeMaker.Return(printVar));
        }


        //设置方法体
        JCTree.JCBlock methodBody = treeMaker.Block(0, statements.toList());
        List<JCTree.JCTypeParameter> methodGenericParams = List.nil();

        //定义入参 T dto
        JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER, List.nil()),
                names.fromString(dto), memberAccess(fan), null);
        //设置入参
        List<JCTree.JCVariableDecl> parameters = List.of(param);
        List<JCTree.JCExpression> throwsClauses = List.nil();

        //构建方法
        return treeMaker.MethodDef(modifiers, methodName,
                "void".equals(returnMethodType.toString()) ?
                        treeMaker.Type((Type) (Class.forName("com.sun.tools.javac.code.Type$JCVoidType").newInstance())) :
                        returnMethodType,
                methodGenericParams, parameters, throwsClauses, methodBody, null);


    }


    // 是否存在数组
    private boolean isExist(String[] strings, String s) {
        for (String s1 : strings) {
            if (s.equals(s1)) {
                return true;
            }
        }
        return false;
    }


    // get方法
    private Name getMethodName(Name name) {
        String s = name.toString();
        return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }


    // 根据字符串获取Name，（利用Names的fromString静态方法）
    private Name getNameFromString(String s) {
        return names.fromString(s);
    }

    // 创建 域/方法 的多级访问, 方法的标识只能是最后一个
    private JCTree.JCExpression memberAccess(String components) {
        String[] componentArray = components.split("\\.");
        JCTree.JCExpression expr = treeMaker.Ident(getNameFromString(componentArray[0]));
        for (int i = 1; i < componentArray.length; i++) {
            expr = treeMaker.Select(expr, getNameFromString(componentArray[i]));
        }
        return expr;
    }


}